/*
 * InvulgoTracker
 * Copyright (C) Thomas Andersen 2011 <thomas.1.andersen@gmail.com>
 * 
 * InvulgoTracker is free software: you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * InvulgoTracker is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License along
 * with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>

#include <config.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <glade/glade.h>

#include <config.h>

#include "gettextmacro.h"

#include "task-view.h"
#include "util.h"
#include "prefs.h"

#include "invulgotrackerapp.h"
#include "taskdetaildlg.h"

#define MY_ENCODING "ISO-8859-1"



TaskView::TaskView()
{
    currentDlg = NULL;
    prefs = new Prefs();
}


TaskView::~TaskView()
{
    Clean();
    delete prefs;
}

void TaskView::Clean()
{
    g_object_unref(model);
    gtk_tree_view_set_model (view, NULL);
    while (gtk_tree_view_remove_column(view, gtk_tree_view_get_column(view, 0)) > 0);
}


extern "C" void treecell_edited_callback (GtkCellRendererText *cell, gchar *path_string, gchar *new_text, gpointer user_data)
{
    InvulgoTrackerApp::app->TaskEdit(path_string, new_text);
}


extern "C" gboolean TaskView::FilterFunc(GtkTreeModel *model, GtkTreeIter *iter, gpointer data)
{
    gboolean done = false;
    gboolean visible = false;

    gtk_tree_model_get (model, iter, COL_DONE, &done, -1);
    if (done && ((TaskView *)data)->prefs->GetHideTaskDone())
        visible = false;
    else
        visible = true;
    return visible;
}

extern "C" void view_popup_menu_onDoAdd(GtkWidget *menuitem, gpointer userdata)
{
    InvulgoTrackerApp::app->TaskAdd();
}

extern "C" void view_popup_menu_onDoAddRoot(GtkWidget *menuitem, gpointer userdata)
{
    InvulgoTrackerApp::app->TaskAddRoot();
}

extern "C" void view_popup_menu_onDoEdit(GtkWidget *menuitem, gpointer userdata)
{
    InvulgoTrackerApp::app->TaskDetails();
}


extern "C" void TaskView::view_popup_menu_onMarkDone(GtkWidget *menuitem, gpointer userdata)
{
    TaskView *t = (TaskView *)userdata;
    gchar *name;
    TaskId id;
    if (t->GetSelected(&name, id)) {
        g_free(name);

        t->SetDone(id, !t->GetDone(id));
        t->Save();
        InvulgoTrackerApp::app->Notify(TaskDetailDlg::SAVED);
    }
}

extern "C" void TaskView::view_popup_menu_onDoEditName(GtkWidget *menuitem, gpointer userdata)
{
    TaskView *t = (TaskView *)userdata;
    GtkTreeView *treeView = t->view;
    GtkTreeSelection *selection;
    GtkTreePath *path = NULL;
    GtkTreeViewColumn *column = NULL;
    GtkTreeIter filterIter;

    selection = gtk_tree_view_get_selection(treeView);
    if (gtk_tree_selection_get_selected(selection, NULL, &filterIter))
    {
        path = gtk_tree_model_get_path(t->modelFilter, &filterIter);
        column = gtk_tree_view_get_column(treeView, 0);
        gtk_tree_view_set_cursor(treeView, path, column, TRUE);
        gtk_tree_path_free(path);
    }
}

extern "C" void view_popup_menu_onDoRemove(GtkWidget *menuitem, gpointer userdata)
{
    InvulgoTrackerApp::app->TaskDelete();
}

extern "C" void view_popup_menu_onDoAddToTime(GtkWidget *menuitem, gpointer userdata)
{
    InvulgoTrackerApp::app->TimeAdd();
}

void TaskView::view_popup_menu (GtkWidget *treeview, GdkEventButton *event, gpointer userdata)
{
    GtkWidget *menu, *menuitem;
    GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(treeview));
    bool selected = gtk_tree_selection_count_selected_rows(selection) == 1;

    /* Create menu */
    menu = gtk_menu_new();

    /* Add items. */
    selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(treeview));

    if (selected) {
        menuitem = gtk_menu_item_new_with_label(_("Add task under this one"));
        g_signal_connect(menuitem, "activate", (GCallback) view_popup_menu_onDoAdd, this);
        gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
    }
    menuitem = gtk_menu_item_new_with_label(_("Add task in root"));
    g_signal_connect(menuitem, "activate", (GCallback) view_popup_menu_onDoAddRoot, this);
    gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
    if (selected) {
        menuitem = gtk_menu_item_new_with_label(_("Edit task name"));
        g_signal_connect(menuitem, "activate", (GCallback) view_popup_menu_onDoEditName, this);
        gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
        menuitem = gtk_menu_item_new_with_label(_("Edit task details"));
        g_signal_connect(menuitem, "activate", (GCallback) view_popup_menu_onDoEdit, this);
        gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
        menuitem = gtk_menu_item_new_with_label(_("Toggle done"));
        g_signal_connect(menuitem, "activate", (GCallback) view_popup_menu_onMarkDone, this);
        gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
        menuitem = gtk_separator_menu_item_new();
        gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
        menuitem = gtk_menu_item_new_with_label(_("Remove task"));
        g_signal_connect(menuitem, "activate", (GCallback) view_popup_menu_onDoRemove, this);
        gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
    }
    if (selected) {
        menuitem = gtk_separator_menu_item_new();
        gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
        menuitem = gtk_menu_item_new_with_label(_("Add to timeview"));
        g_signal_connect(menuitem, "activate", (GCallback) view_popup_menu_onDoAddToTime, this);
        gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
    }


    gtk_widget_show_all(menu);
    gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL,(event != NULL) ? event->button : 0,gdk_event_get_time((GdkEvent*)event));
}


extern "C" gboolean on_treeviewTask_button_press_event(GtkWidget *treeview, GdkEventButton *event, gpointer userdata)
{
    /* single click with the right mouse button? */
    if (event->type == GDK_BUTTON_PRESS  &&  event->button == 3)
    {
        GtkTreeSelection *selection;

        selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(treeview));
        if (gtk_tree_selection_count_selected_rows(selection)  <= 1)
        {
           GtkTreePath *path;

           /* Get tree path for row that was clicked */
           if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(treeview),
                                             (gint) event->x,
                                             (gint) event->y,
                                             &path, NULL, NULL, NULL))
           {
             gtk_tree_selection_unselect_all(selection);
             gtk_tree_selection_select_path(selection, path);
             gtk_tree_path_free(path);
           } else
             gtk_tree_selection_unselect_all(selection);
        }
        InvulgoTrackerApp::app->GetTaskView().view_popup_menu(treeview, event, userdata);
        return TRUE; /* we handled this */
    }

    return FALSE; /* we did not handle this */
}


extern "C" gboolean on_treeviewTask_popup_menu (GtkWidget *treeview, gpointer userdata)
{
    InvulgoTrackerApp::app->GetTaskView().view_popup_menu(treeview, NULL, userdata);

    return TRUE; /* we handled this */
}


void TaskView::Refilter()
{
    delete prefs;
    prefs = new Prefs();
    gtk_tree_model_filter_refilter(GTK_TREE_MODEL_FILTER(modelFilter));
}

void TaskView::Restart()
{
    Clean();
    Init(GTK_WIDGET(view));
}

void TaskView::Init(GtkWidget *view)
{
    GtkCellRenderer     *renderer;

    this->view = GTK_TREE_VIEW(view);

    this->model = GTK_TREE_MODEL(gtk_tree_store_new(NUM_COLS, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_LONG, G_TYPE_LONG, G_TYPE_LONG, G_TYPE_LONG, G_TYPE_BOOLEAN, G_TYPE_BOOLEAN, G_TYPE_STRING));

    renderer = gtk_cell_renderer_text_new ();
    g_object_set(renderer, "editable",TRUE, NULL);
    g_signal_connect(renderer, "edited", (GCallback) treecell_edited_callback, this->model);
    gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (view), -1, "Task",  renderer,  "text", COL_NAME, "cell-background", COL_COLOR, NULL);

    modelFilter = gtk_tree_model_filter_new(model, NULL);
    gtk_tree_model_filter_set_visible_func(GTK_TREE_MODEL_FILTER(modelFilter), FilterFunc, this, NULL);
    Load();
    gtk_tree_view_set_model (GTK_TREE_VIEW (view), modelFilter);
    gtk_tree_view_expand_all(GTK_TREE_VIEW (view));
}

void TaskView::BuildMenu(GtkWidget *parentItem, GCallback callback, list<TaskId *> &menuTaskList)
{
    GtkTreeIter iter;
    if (gtk_tree_model_get_iter_first(model, &iter)) {
        BuildSubMenu(&iter, parentItem, callback, menuTaskList);
    }
}

void TaskView::BuildSubMenu(GtkTreeIter *parent, GtkWidget *parentItem, GCallback callback, list<TaskId *> &menuTaskList)
{
    GtkTreeIter iter;
    gchar *name;
    gchar *value;
    GtkWidget *menu = gtk_menu_new();

    gtk_menu_item_set_submenu (GTK_MENU_ITEM (parentItem), menu);

    do {
        GtkWidget *item;
        gtk_tree_model_get(model, parent,
                           0, &name,
                           1, &value,
                           -1);
        item = gtk_menu_item_new_with_label(name);
        g_free(name);
        TaskId *ti = new TaskId(value);
        g_free(value);
        gtk_widget_show(item);
        gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
        
        if (gtk_tree_model_iter_children(model, &iter, parent)) {
            delete ti;
            BuildSubMenu(&iter, item, callback, menuTaskList);
        } else {
            g_signal_connect(GTK_OBJECT (item), "activate", callback, (void*)ti);
            menuTaskList.push_back(ti);
        }

    } while(gtk_tree_model_iter_next(model, parent));
}


void TaskView::Save()
{
    int rc;
    xmlTextWriterPtr writer;
    xmlChar *tmp;
    xmlDocPtr doc;

    /* Create a new XmlWriter for DOM, with no compression. */
    writer = xmlNewTextWriterDoc(&doc, 0);
    if (writer == NULL) return;

    rc = xmlTextWriterStartDocument(writer, NULL, MY_ENCODING, NULL);
    if (rc < 0) return;

    rc = xmlTextWriterStartElement(writer, BAD_CAST "TaskList");
    if (rc < 0) return;

    tmp = ConvertInput("List of tasks for InvulgoTracker. See www.invulgo.dk for details.", MY_ENCODING);
    rc = xmlTextWriterWriteComment(writer, tmp);
    if (tmp != NULL) xmlFree(tmp);
    if (rc < 0) return;

    GtkTreeIter iter;
    if (gtk_tree_model_get_iter_first(model, &iter)) {
        StoreTask(writer, &iter, TaskId());
    }
    rc = xmlTextWriterEndElement(writer);
    if (rc < 0) return;

    rc = xmlTextWriterEndDocument(writer);
    if (rc < 0) return;

    xmlFreeTextWriter(writer);

    const gchar *homeDir = g_get_home_dir();
    gchar *filename = new gchar[PATH_MAX+1];
    strcpy(filename,homeDir);
    strcat(filename, "/.InvulgoTracker/tasks.xml");
    xmlSaveFormatFile(filename, doc, 1);
    delete[] filename;
    
    xmlFreeDoc(doc);
}

void TaskView::StoreTask(xmlTextWriterPtr writer, GtkTreeIter *parent, TaskId parid)
{
    GtkTreeIter iter;
    gchar *name;
    gchar *value;
    xmlChar *tmp;
    glong bestCase, mostLikely, worstCase;
    glong deadline;
    gboolean hasDeadline;
    gboolean done;

    do {
        gtk_tree_model_get(model, parent,
                           COL_NAME, &name,
                           COL_ID, &value,
                           COL_BEST, &bestCase,
                           COL_LIKELY, &mostLikely,
                           COL_WORST, &worstCase,
                           COL_DEADLINE, &deadline,
                           COL_HASDEADLINE, &hasDeadline,
                           COL_DONE, &done,
                           -1);
        xmlTextWriterStartElement(writer, BAD_CAST "Task");
        tmp = ConvertInput(name, MY_ENCODING);
        xmlTextWriterWriteFormatElement(writer, BAD_CAST "Name","%s", tmp);
        if (tmp != NULL) xmlFree(tmp);
        xmlTextWriterWriteFormatElement(writer, BAD_CAST "Id","%s", value);
        TaskId ti(value);
        g_free(value);
        xmlTextWriterWriteFormatElement(writer, BAD_CAST "ParentId","%s", parid?(const gchar *)parid:"0");
        xmlTextWriterWriteFormatElement(writer, BAD_CAST "EstBestCase","%ld", bestCase);
        xmlTextWriterWriteFormatElement(writer, BAD_CAST "EstMostLikely","%ld", mostLikely);
        xmlTextWriterWriteFormatElement(writer, BAD_CAST "EstWorstCase","%ld", worstCase);
        xmlTextWriterWriteFormatElement(writer, BAD_CAST "Deadline","%ld", deadline);
        xmlTextWriterWriteFormatElement(writer, BAD_CAST "HasDeadline","%ld", (long)hasDeadline);
        xmlTextWriterWriteFormatElement(writer, BAD_CAST "Done","%ld", (long)done);
        xmlTextWriterEndElement(writer);
        if (gtk_tree_model_iter_children(model, &iter, parent)) {
            StoreTask(writer, &iter, ti);
        }
    } while(gtk_tree_model_iter_next(model, parent));
}


void TaskView::Load()
{
    xmlDocPtr doc;

    const gchar *homeDir = g_get_home_dir();
    gchar *filename = new gchar[PATH_MAX+1];
    strcpy(filename,homeDir);
    strcat(filename, "/.InvulgoTracker/tasks.xml");
    doc = xmlParseFile(filename);
    delete[] filename;
    
    if (doc) {
        xmlNodePtr cur;
        cur = xmlDocGetRootElement(doc);
        if (!xmlStrcmp(cur->name, (const xmlChar *) "TaskList")) {
            cur = cur->xmlChildrenNode;
            while (cur != NULL) {
                if ((!xmlStrcmp(cur->name, (const xmlChar *)"Task"))){
                    ParseTask (doc, cur);
                }

            cur = cur->next;
            }
        }
        xmlFreeDoc(doc);
    }
}

void TaskView::ParseTask(xmlDocPtr doc, xmlNodePtr cur)
{
    xmlChar *name = NULL;
    xmlChar *key;
    TaskId id;
    TaskId parid;
    glong bestCase = 0, mostLikely = 0, worstCase = 0;
    glong deadline = 0;
    gboolean hasDeadline = false;
    gboolean done = false;

    cur = cur->xmlChildrenNode;
    while (cur != NULL) {
        if ((!xmlStrcmp(cur->name, (const xmlChar *)"Name"))) {
            name = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
            gchar *tmp = ConvertOutput (name, MY_ENCODING);
            if (tmp)
            {
                xmlFree(name);
                name = (xmlChar*)tmp;
            }
         }
        if ((!xmlStrcmp(cur->name, (const xmlChar *)"Id"))) {
            key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
            id = TaskId((gchar *)key);
            xmlFree(key);
         }
        if ((!xmlStrcmp(cur->name, (const xmlChar *)"ParentId"))) {
            key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
            parid = TaskId((gchar *)key);
            xmlFree(key);
         }
        if ((!xmlStrcmp(cur->name, (const xmlChar *)"EstBestCase"))) {
            key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
            bestCase = atol((char *)key);
            xmlFree(key);
         }
        if ((!xmlStrcmp(cur->name, (const xmlChar *)"EstMostLikely"))) {
            key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
            mostLikely = atol((char *)key);
            xmlFree(key);
         }
        if ((!xmlStrcmp(cur->name, (const xmlChar *)"EstWorstCase"))) {
            key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
            worstCase = atol((char *)key);
            xmlFree(key);
         }
        if ((!xmlStrcmp(cur->name, (const xmlChar *)"Deadline"))) {
            key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
            deadline = atol((char *)key);
            xmlFree(key);
         }
        if ((!xmlStrcmp(cur->name, (const xmlChar *)"HasDeadline"))) {
            key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
            hasDeadline = (gboolean)atol((char *)key);
            xmlFree(key);
         }
        if ((!xmlStrcmp(cur->name, (const xmlChar *)"Done"))) {
            key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
            done = (gboolean)atol((char *)key);
            xmlFree(key);
         }
    cur = cur->next;
    }
    GtkTreeIter iter, parent;

    gtk_tree_model_get_iter_first(model, &parent);

    const gchar *color = NULL;
    Prefs prefs;

    if (done)
        color = prefs.GetColorDone();
    else if (hasDeadline) {
        TaskTime tt;
        glong now = (tt.GetYear()*10000)+(tt.GetMonth()*100)+tt.GetDay();
        if (now >= deadline)
            color = prefs.GetColorDeadline();
    }

    if ((const gchar *)parid && FindTaskFromPos(parid, &parent)) {
        gtk_tree_store_append(GTK_TREE_STORE(model), &iter, &parent);
        gtk_tree_store_set (GTK_TREE_STORE(model), &iter, COL_NAME, name, COL_ID, (const gchar *)id, COL_BEST, bestCase, COL_LIKELY, mostLikely, COL_WORST, worstCase, COL_DEADLINE, deadline, COL_HASDEADLINE, hasDeadline, COL_DONE, done, COL_COLOR, color, -1);
    } else {
        gtk_tree_store_append(GTK_TREE_STORE(model), &iter, NULL);
        gtk_tree_store_set (GTK_TREE_STORE(model), &iter, COL_NAME, name, COL_ID, (const gchar *)id, COL_BEST, bestCase, COL_LIKELY, mostLikely, COL_WORST, worstCase, COL_DEADLINE, deadline, COL_HASDEADLINE, hasDeadline, COL_DONE, done, COL_COLOR, color, -1);
    }
    if (name)
        xmlFree(name);
}

TaskId TaskView::GetTaskId(GtkTreeIter *iter)
{
    gchar *value;
    gtk_tree_model_get(model, iter,
                       1, &value,
                       -1);

    TaskId ti(value);
    g_free(value);

    return ti;
}

gboolean TaskView::FindTask(GtkTreeIter *iter, TaskId &taskId)
{
    if (gtk_tree_model_get_iter_first(model, iter))
        return FindTaskFromPos(taskId, iter);
    return FALSE;
}

gboolean TaskView::FindTaskFromPos(TaskId &parid, GtkTreeIter *iter)
{
    GtkTreeIter child;
    gchar *value;

    do {
        gtk_tree_model_get(model, iter,
                           1, &value,
                           -1);
        TaskId ti(value);
        g_free(value);
        if (parid == ti)
            return TRUE;
        if (gtk_tree_model_iter_children(model, &child, iter)) {
            gboolean rc = FindTaskFromPos(parid, &child);
            if (rc)
            {
                *iter = child;
                return rc;
            }
        }
    } while(gtk_tree_model_iter_next(model, iter));

    return FALSE;
}


void TaskView::Add()
{
    GtkTreeSelection *selection;
    GtkTreeIter       iter, child, iterCheck,filterIter;

    selection = gtk_tree_view_get_selection(this->view);
    if (gtk_tree_selection_get_selected(selection, NULL, &filterIter))
    {
        gtk_tree_model_filter_convert_iter_to_child_iter(GTK_TREE_MODEL_FILTER(modelFilter), &iter, &filterIter);
        gtk_tree_store_append(GTK_TREE_STORE(model), &child, &iter);
        TaskId ti;
        /* Make sure task id is unique. */
        do {
            ti.Generate();
        } while(FindTask(&iterCheck, ti));

        gtk_tree_store_set(GTK_TREE_STORE(model), &child, COL_NAME, _("New task"), COL_ID, (const gchar *)ti, -1);
    }
}

extern GladeXML *gxml;

bool TaskView::WarnDelete()
{
    Prefs prefs;

    if (prefs.GetWarnOnTimeDelete()) {
        currentDlg = glade_xml_get_widget (InvulgoTrackerApp::gxml, "dialogTaskDeleteWarning");
        gint result = gtk_dialog_run (GTK_DIALOG (currentDlg));
        gtk_widget_hide(currentDlg);
        currentDlg = NULL;
        if (result != GTK_RESPONSE_OK) {
            return FALSE;
        }
    }
    return TRUE;
}

void TaskView::Delete()
{
    GtkTreeSelection *selection;
    GtkTreeIter       iter, filterIter;

    if (WarnDelete()) {
        selection = gtk_tree_view_get_selection(this->view);
        if (gtk_tree_selection_get_selected(selection, NULL, &filterIter))
        {
            gtk_tree_model_filter_convert_iter_to_child_iter(GTK_TREE_MODEL_FILTER(modelFilter), &iter, &filterIter);
            gtk_tree_store_remove(GTK_TREE_STORE(model), &iter);
        }
    }
}

void TaskView::AddRoot()
{
    GtkTreeIter iter, iterCheck;

    gtk_tree_store_append(GTK_TREE_STORE(model), &iter, NULL);
    TaskId ti;
    /* Make sure task id is unique. */
    do {
        ti.Generate();
    } while(FindTask(&iterCheck, ti));
    gtk_tree_store_set(GTK_TREE_STORE(model), &iter, COL_NAME, _("New task"), COL_ID, (const gchar *)ti, -1);
}


void TaskView::Edit(gchar *pathStr, gchar *newStr)
{
    GtkTreeIter iter;
    GtkTreePath *modelPath;
    GtkTreePath *path;
    gchar *str;

    path = gtk_tree_path_new_from_string(pathStr);
    modelPath = gtk_tree_model_filter_convert_path_to_child_path(GTK_TREE_MODEL_FILTER(modelFilter), path);
    str = gtk_tree_path_to_string(modelPath);
    gtk_tree_path_free(modelPath);
    gtk_tree_path_free(path);

    gtk_tree_model_get_iter_from_string(model, &iter, str);
    g_free(str);
    gtk_tree_store_set (GTK_TREE_STORE(model), &iter, COL_NAME, newStr, -1);
}

glong TaskView::GetBestCase(TaskId &id)
{
    GtkTreeIter       iter;

    if (FindTask(&iter, id)) {
        glong bestCase = 0;
        gtk_tree_model_get(model, &iter, COL_BEST, &bestCase, -1);
        return bestCase;
    }
    return 0;
}

glong TaskView::GetMostLikely(TaskId &id)
{
    GtkTreeIter       iter;

    if (FindTask(&iter, id)) {
        glong mostLikely = 0;
        gtk_tree_model_get(model, &iter, COL_LIKELY, &mostLikely, -1);
        return mostLikely;
    }
    return 0;
}


glong TaskView::GetWorstCase(TaskId &id)
{
    GtkTreeIter       iter;

    if (FindTask(&iter, id)) {
        glong worstCase = 0;
        gtk_tree_model_get(model, &iter, COL_WORST, &worstCase, -1);
        return worstCase;
    }
    return 0;
}

void TaskView::SetBestCase(TaskId &id, glong bestCase)
{
    GtkTreeIter       iter;

    if (FindTask(&iter, id)) {
        gtk_tree_store_set(GTK_TREE_STORE(model), &iter, COL_BEST, bestCase, -1);
    }
}

void TaskView::SetMostLikely(TaskId &id, glong mostLikely)
{
    GtkTreeIter       iter;

    if (FindTask(&iter, id)) {
        gtk_tree_store_set(GTK_TREE_STORE(model), &iter, COL_LIKELY, mostLikely, -1);
    }
}

void TaskView::SetWorstCase(TaskId &id, glong worstCase)
{
    GtkTreeIter       iter;

    if (FindTask(&iter, id)) {
        gtk_tree_store_set(GTK_TREE_STORE(model), &iter, COL_WORST, worstCase, -1);
    }
}


void TaskView::UpdateColor(GtkTreeIter *iter)
{
    const gchar *color = NULL;
    glong deadline = 0;
    gboolean hasDeadline;
    gboolean done;
    Prefs prefs;

    gtk_tree_model_get(model, iter, COL_DEADLINE, &deadline, COL_HASDEADLINE, &hasDeadline, COL_DONE, &done, COL_DEADLINE, &deadline, -1);

    if (done)
        color = prefs.GetColorDone();
    else if (hasDeadline) {
        TaskTime tt;
        glong now = (tt.GetYear()*10000)+(tt.GetMonth()*100)+tt.GetDay();
        if (now >= deadline)
            color = prefs.GetColorDeadline();
    }
    gtk_tree_store_set(GTK_TREE_STORE(model), iter, COL_COLOR, color, -1);
}

void TaskView::SetDeadline(TaskId &id, glong deadline)
{
    GtkTreeIter       iter;


    if (FindTask(&iter, id)) {
        gtk_tree_store_set(GTK_TREE_STORE(model), &iter, COL_DEADLINE, deadline, -1);
        UpdateColor(&iter);
    }
}

glong TaskView::GetDeadline(TaskId &id)
{
    GtkTreeIter       iter;

    if (FindTask(&iter, id)) {
        glong deadline = 0;
        gtk_tree_model_get(model, &iter, COL_DEADLINE, &deadline, -1);
        return deadline;
    }
    return 0;
}

void TaskView::SetHasDeadline(TaskId &id, gboolean hasDeadline)
{
    GtkTreeIter       iter;

    if (FindTask(&iter, id)) {
        gtk_tree_store_set(GTK_TREE_STORE(model), &iter, COL_HASDEADLINE, hasDeadline, -1);
        UpdateColor(&iter);
    }
}

gboolean TaskView::GetHasDeadline(TaskId &id)
{
    GtkTreeIter       iter;

    if (FindTask(&iter, id)) {
        gboolean hasDeadline = false;
        gtk_tree_model_get(model, &iter, COL_HASDEADLINE, &hasDeadline, -1);
        return hasDeadline;
    }
    return 0;
}

void TaskView::SetDone(TaskId &id, gboolean done)
{
    GtkTreeIter       iter;

    if (FindTask(&iter, id)) {
        gtk_tree_store_set(GTK_TREE_STORE(model), &iter, COL_DONE, done, -1);
        UpdateColor(&iter);
    }
}

gboolean TaskView::GetDone(TaskId &id)
{
    GtkTreeIter       iter;

    if (FindTask(&iter, id)) {
        gboolean done = false;
        gtk_tree_model_get(model, &iter, COL_DONE, &done, -1);
        return done;
    }
    return 0;
}

bool TaskView::GetSelected(gchar **name, TaskId &id)
{
    GtkTreeSelection *selection;
    GtkTreeIter       iter,filterIter;
    gchar *value;

    selection = gtk_tree_view_get_selection(this->view);
    if (gtk_tree_selection_get_selected(selection, NULL, &filterIter))
    {
        gtk_tree_model_filter_convert_iter_to_child_iter(GTK_TREE_MODEL_FILTER(modelFilter), &iter, &filterIter);
        gtk_tree_model_get(model, &iter,
                           COL_NAME, name,
                           COL_ID, &value,
                           -1);
        id = TaskId(value);
        g_free(value);
        QualifyParentNames(&iter, name);
        return TRUE;
    }        
    return FALSE;
}

bool TaskView::Get(GtkTreePath *path, gchar **name, TaskId &id)
{
    GtkTreeIter       iter;
    gchar *value;
    GtkTreePath *modelPath;

    modelPath = gtk_tree_model_filter_convert_path_to_child_path(GTK_TREE_MODEL_FILTER(modelFilter), path);

    if (gtk_tree_model_get_iter(model, &iter, modelPath))
    {
        gtk_tree_model_get(model, &iter,
                           COL_NAME, name,
                           COL_ID, &value,
                           -1);
        id = TaskId(value);
        g_free(value);
        QualifyParentNames(&iter, name);
        gtk_tree_path_free(modelPath);
        return TRUE;
    }        
    return FALSE;
}

bool TaskView::Get(TaskId id, gchar **name)
{
    GtkTreeIter       iter;

    gtk_tree_model_get_iter_first(model, &iter);

    if (FindTaskFromPos(id, &iter)) {
        gtk_tree_model_get(model, &iter,
                           COL_NAME, name,
                           -1);
        QualifyParentNames(&iter, name);
        return TRUE;
    }
    return FALSE;
}

void TaskView::QualifyParentNames(GtkTreeIter *iter, gchar **name)
{
    GtkTreeIter parent;
    while(gtk_tree_model_iter_parent(GTK_TREE_MODEL(model), &parent, iter))
    {
        gchar *parName;
        gchar *tmp;
        gtk_tree_model_get(model, &parent, COL_NAME, &parName, -1);
        tmp = (gchar*)g_malloc(strlen(parName)+strlen(*name) + 5);
        strcpy(tmp, parName);
        strcat(tmp, " -> ");
        strcat(tmp, *name);
        g_free(*name);
        g_free(parName);
        *name = tmp;
        *iter = parent;
    }
}


bool TaskView::GetParent(TaskId &id, gchar **name)
{
    GtkTreeIter iter;
    GtkTreeIter parent;

    gtk_tree_model_get_iter_first(model, &iter);

    if (FindTaskFromPos(id, &iter) && gtk_tree_model_iter_parent(GTK_TREE_MODEL(model), &parent, &iter)) {
        gchar *value;
        gtk_tree_model_get(model, &parent, COL_NAME, name, COL_ID, &value,  -1);
        id = TaskId(value);
        g_free(value);
        QualifyParentNames(&parent, name);
        return TRUE;
    }
    return FALSE;
}


glong TaskView::GetEstimate95(TaskId &id)
{
    glong bestCase = GetBestCase(id);
    glong mostLikely = GetMostLikely(id);
    glong worstCase = GetWorstCase(id);
    glong conf95 = 0;

    if (bestCase || mostLikely || worstCase) {
        gdouble E = ((gdouble)bestCase + (gdouble)(4*mostLikely) + (gdouble)worstCase) / 6.0;
        gdouble SD = ((gdouble)worstCase - (gdouble)bestCase) / 6.0;
        conf95 = (glong)(E + (2*SD));
    } else { /* Since nothing here, then try children. */
        GtkTreeIter iter, child;
        if (FindTask(&iter, id)) {
            if (gtk_tree_model_iter_children(model, &child, &iter)) {
                do {
                    gchar *value;
                    gtk_tree_model_get(model, &child, COL_ID, &value,  -1);
                    TaskId childId = TaskId(value);
                    g_free(value);
                    conf95 += GetEstimate95(childId);
                } while(gtk_tree_model_iter_next(model, &child));
            }
        }
    }

    return conf95;
}


